import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import {
    normalizeRepoBranches,
    normalizeRepoContent,
    RepoBranchesApi,
    RepoContentApi,
    RepoContentModel,
} from 'store/models';
import RootStore, { IGlobalStore } from 'store/RootStore';
import ApiStore, { FetchMeta } from 'store/RootStore/ApiStore';

import { Option } from 'components/MultiDropdown';
import { filesSort, searchCreate } from 'utils';

type PrivateFields =
    | '_repoDirContent'
    | '_branches'
    | '_topLevel'
    | '_dropdownValue'
    | '_prevDirUrl'
    | '_owner'
    | '_repo'
    | '_breadCrumbs'
    | '_repoDirMeta'
    | '_branchesMeta'
    | '_fetchRequestsMeta';

export default class RepoContentStore implements IGlobalStore {
    private readonly _ApiStore = new ApiStore();
    private _owner = '';
    private _repo = '';
    private _prevDirUrl?: string;
    private _topLevel = true;
    private _breadCrumbs: { dir: string; src: string }[] = [];
    private _repoDirContent: RepoContentModel[] = [];
    private _branches: Option[] = [];
    private _dropdownValue: Option[] = [{ key: 'main', value: 'main' }];
    private _repoDirMeta = FetchMeta.INITIAL;
    private _branchesMeta = FetchMeta.INITIAL;
    private readonly _fetchMetaReaction: IReactionDisposer;
    private readonly _pathParamReaction: IReactionDisposer;

    constructor(owner: string, repo: string) {
        makeObservable<RepoContentStore, PrivateFields>(this, {
            _repoDirMeta: observable,
            _branchesMeta: observable,
            _prevDirUrl: observable,
            _topLevel: observable,
            _breadCrumbs: observable.ref,
            _owner: observable,
            _repo: observable,
            _dropdownValue: observable.ref,
            _branches: observable.ref,
            _repoDirContent: observable.ref,
            _fetchRequestsMeta: computed,
            dropdownValue: computed,
            repoBranches: computed,
            breadCrumbs: computed,
            repoDirContent: computed,
            prevDirUrl: computed,
            topLevel: computed,
            fetchRepoDir: action.bound,
            setDropdownValue: action.bound,
            fetchBranches: action.bound,
        });

        this._owner = owner;
        this._repo = repo;

        const searchParams = new URLSearchParams(window.location.search);
        const path = searchParams.get('path');
        const ref = searchParams.get('ref');
        this.fetchRepoDir(
            `https://api.github.com/repos/${owner}/${repo}/contents${path ?? '/'}${searchCreate(
                {
                    ref: ref,
                },
                true,
            )}`,
        );
        this.fetchBranches(owner, repo);
        this._breadCrumbs = [
            { dir: repo, src: `${window.location.origin}/${owner}/${repo}/${searchCreate({ ref: ref }, true)}` },
        ];

        this._fetchMetaReaction = reaction(
            () => this._fetchRequestsMeta,
            (meta) => {
                RootStore.pageLoader.getFetchRequestsMeta(meta);
            },
        );

        this._pathParamReaction = reaction(
            () => [RootStore.locationStore._params.get('path'), RootStore.locationStore._params.get('ref')],
            (params) => {
                this.fetchRepoDir(
                    `https://api.github.com/repos/${owner}/${repo}/contents${params[0] ?? '/'}${searchCreate(
                        {
                            ref: params[1],
                        },
                        true,
                    )}`,
                );
            },
        );
    }

    get dropdownValue(): Option[] {
        return this._dropdownValue;
    }

    setDropdownValue = (value: Option[]): void => {
        this._dropdownValue = value;
    };

    get repoBranches(): Option[] {
        return this._branches;
    }

    get breadCrumbs(): { dir: string; src: string }[] {
        return this._breadCrumbs;
    }
    get repoDirContent(): RepoContentModel[] {
        return this._repoDirContent;
    }

    get prevDirUrl(): string | undefined {
        return this._prevDirUrl;
    }

    get topLevel(): boolean {
        return this._topLevel;
    }

    private get _fetchRequestsMeta(): FetchMeta[] {
        return [this._repoDirMeta, this._branchesMeta];
    }

    fetchRepoDir = async (url: string): Promise<void> => {
        this._repoDirMeta = FetchMeta.FETCHING;
        try {
            const result = await this._ApiStore.getApiRequest<RepoContentApi[]>(url, { baseURL: '' });
            runInAction(() => {
                this._repoDirContent = normalizeRepoContent(result.data).sort(filesSort);
                let ref = url.match(/(?:[?&]ref=)(.*)/)?.[1] ?? '';
                ref = ref.match(/(.*)(?=&)/)?.[1] ?? ref;
                this._prevDirUrl = (url.match(/.*(?=\/)/)?.[0] ?? '') + searchCreate({ ref: ref }, true);
                this._topLevel =
                    this._repoDirContent[0] && this._repoDirContent[0]?.name === this._repoDirContent[0]?.path;
                let path = ('/' + this._repoDirContent[0]?.path).match(/.*(?=\/)/)?.[0] ?? '';
                this._breadCrumbs = [];
                while (path.length) {
                    const reg = path.match(/\/([^\/]+)$/);
                    if (!reg) {
                        break;
                    }
                    this._breadCrumbs.unshift({
                        dir: reg?.[1],
                        src: `/${this._owner}/${this._repo}/${searchCreate({ path: path, ref: ref }, true)}`,
                    });
                    path = path.match(/.*(?=\/)/)?.[0] ?? '';
                }
                this._breadCrumbs.unshift({
                    dir: this._repo,
                    src: `${window.location.origin}/${this._owner}/${this._repo}/${searchCreate(
                        {
                            path: '/',
                            ref: ref,
                        },
                        true,
                    )}`,
                });
                this._repoDirMeta = FetchMeta.FETCHED;
            });
        } catch {
            runInAction(() => {
                this._repoDirMeta = FetchMeta.ERROR;
                this._repoDirContent = [];
            });
        }
    };

    fetchBranches = async (owner: string, repo: string): Promise<void> => {
        this._branchesMeta = FetchMeta.FETCHING;
        try {
            const result = await this._ApiStore.getApiRequest<RepoBranchesApi[]>(`/repos/${owner}/${repo}/branches`);
            runInAction(() => {
                this._branches = normalizeRepoBranches(result.data);
                const ref = new URLSearchParams(window.location.search).get('ref');
                if (ref) {
                    this._dropdownValue = [{ key: ref, value: ref }];
                } else {
                    const branch = this._branches.find((value) => value.value === 'main' || value.value === 'master');
                    if (branch) {
                        this._dropdownValue = [branch];
                    } else {
                        this._dropdownValue = [this._branches[0]];
                    }
                }
                this._branchesMeta = FetchMeta.FETCHED;
            });
        } catch {
            runInAction(() => {
                this._branchesMeta = FetchMeta.ERROR;
                this._branches = [];
            });
        }
    };

    destroy(): void {
        this._fetchMetaReaction();
        this._pathParamReaction();
    }
}
